package lumis.upgrade;

import lumis.portal.*;
import lumis.portal.acl.PortalPermissions;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.channel.ChannelConfig;
import lumis.portal.dao.DaoFactory;
import lumis.portal.manager.*;
import lumis.portal.serialization.DeserializationContext;
import lumis.portal.serialization.DeserializationContext.ExistingStyleAction;
import lumis.portal.serialization.persistency.zip.ZipDirectory;
import lumis.portal.user.UserConfig;
import lumis.portal.util.PortalUtilInternal;
import lumis.util.*;

import java.io.FileNotFoundException;
import java.util.List;
import java.util.Vector;

import org.w3c.dom.*;

/**
 * Manager for upgrade operations on the portal framework.
 * 
 * @since 4.0.0
 * @version $Revision: 12710 $ $Date: 2011-03-18 17:00:16 -0300 (Fri, 18 Mar 2011) $
 */
public class UpgradeManager implements IUpgradeManager
{
	public static final String ACTION_TYPE_RUN_SQL = "RunSql";
	
	/** Indicates that a custom step handler should be executed. */
	public static final String ACTION_TYPE_CUSTOM = "Custom";

	/**
	 * Indicates that the step handler for registering a service should be executed.
	 * @since 4.0.4
	 * @see RegisterServiceStepHandler
	 */
	public static final String ACTION_TYPE_REGISTER_SERVICE = "RegisterService";
	
	/**
	 * Indicates that the step handler for unregister a control type should be executed.
	 * @since 4.0.6
	 * @see lumis.doui.upgrade.UnregisterControlTypeStepHandler
	 */
	public static final String ACTION_TYPE_UNREGISTER_CONTROL_TYPE = "UnregisterControlType";
	
	public static final int BUILD_STATUS_EXECUTING = 0;
	public static final int BUILD_STATUS_SUCCESS = 1;
	public static final int BUILD_STATUS_ERROR = 2;

	public static final int USER_RESPONSE_EXECUTION_REQUESTED = 0;
	public static final int USER_RESPONSE_EXECUTION_SKIPPED = 1;

	private Node getUpgradeNode() throws ManagerException, PortalException
	{
		try
		{
			Document upgradeInfo = PortalUtilInternal.getDefinitionDocument("lumis/upgrade/upgrade.xml");
			Node upgradeNode = upgradeInfo.getDocumentElement();

			return upgradeNode;
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	private BuildConfig getNextBuildConfig(Node upgradeNode, BuildConfig currentBuild) throws ManagerException, PortalException
	{
		if (currentBuild == null)
		{
			// A current build is required
			throw new UnexpectedException("Upgrade not supported");
		}

		// TODO: Change this logic to include majorVersion, minorVersion, releaseVersion
		// conditions when selectSingleNode supports more than 2
		// conditions at once

		Node currentBuildNode = XmlUtil.selectSingleNode("/upgradeConfiguration/build[@majorVersion='" + 
				currentBuild.getMajorVersion() + "' and @minorVersion='" +
				currentBuild.getMinorVersion() + "' and @releaseVersion='" +
				currentBuild.getReleaseVersion() + "' and @buildNumber='" + 
				currentBuild.getBuildNumber() + "' and @buildSequence='" + 
				currentBuild.getBuildSequence() + "']", upgradeNode);
		if (currentBuildNode == null)
		{
			// The current build not found for this upgrade
			throw new UnexpectedException("Upgrade not supported from build " + currentBuild.toString());
		}

		// get next build
		Node nextBuildNode = currentBuildNode.getNextSibling();
		while (nextBuildNode != null && nextBuildNode.getNodeType() == Node.TEXT_NODE)
			nextBuildNode = nextBuildNode.getNextSibling();

		if (nextBuildNode == null)
			return null;
		BuildConfig nextBuild = new BuildConfig(nextBuildNode);

		if (XmlUtil.selectSingleNode("message", nextBuildNode) != null)
		{
			nextBuild.setMessage(XmlUtil.readNodeString("message", nextBuildNode));
			nextBuild.setMessageType(XmlUtil.readAttributeString("type", XmlUtil.selectSingleNode("message", nextBuildNode)));
		}

		return nextBuild;
	}

	public String getNextBuild(String userSessionId, String buildNumber, String buildSequence, ITransaction transaction) throws ManagerException, PortalException
	{
		Node upgradeNode = getUpgradeNode();

		BuildConfig currentBuild = null;
		BuildConfig nextBuild = null;

		if (buildNumber != null && buildSequence != null)
		{
			currentBuild = new BuildConfig();

			currentBuild.setBuildNumber(buildNumber);
			currentBuild.setBuildSequence(Integer.parseInt(buildSequence));
		}

		nextBuild = getNextBuildConfig(upgradeNode, currentBuild);

		// Monta XML de resposta
		StringBuilder nextBuildInfo = new StringBuilder();

		if (nextBuild != null)
		{
			nextBuildInfo.append("<nextBuild>");
			nextBuildInfo.append("<buildNumber>");
			nextBuildInfo.append(nextBuild.getBuildNumber());
			nextBuildInfo.append("</buildNumber>");
			nextBuildInfo.append("<buildSequence>");
			nextBuildInfo.append(nextBuild.getBuildSequence());
			nextBuildInfo.append("</buildSequence>");

			if (nextBuild.getMessage() != null)
			{
				nextBuildInfo.append("<message");

				if (nextBuild.getMessageType() != null)
					nextBuildInfo.append(" type=\"" + nextBuild.getMessageType() + "\">");
				else
					nextBuildInfo.append(">");

				nextBuildInfo.append(nextBuild.getMessage());
				nextBuildInfo.append("</message>");
			}

			nextBuildInfo.append("</nextBuild>");
		}

		return nextBuildInfo.toString();
	}

	private String executeBuild(SessionConfig sessionConfig, BuildConfig currentBuild, 
			ITransaction transaction) throws PortalException
	{
		try
		{
			Node upgradeNode = getUpgradeNode();

			Node buildNode = XmlUtil.selectSingleNode("/upgradeConfiguration/build[@majorVersion='" + 
					currentBuild.getMajorVersion() + "' and @minorVersion='" +
					currentBuild.getMinorVersion() + "' and @releaseVersion='" +
					currentBuild.getReleaseVersion() + "' and @buildNumber='" + 
					currentBuild.getBuildNumber() + "' and @buildSequence='" + 
					currentBuild.getBuildSequence() + "']", upgradeNode);
			if (buildNode == null)
			{
				// The current build not found for this upgrade
				throw new UnexpectedException("Upgrade not supported from build " + currentBuild.toString());
			}

			Node[] stepNodes = XmlUtil.selectNodes("steps/step", buildNode);
			Vector<Node> stepResultNodes = new Vector<Node>();

			for (Node stepNode : stepNodes)
			{
				Node stepResultNode = null;
				String implementationClass = null;
				IStepHandler stepHandler = null;

				String actionType = XmlUtil.readNodeString("actionType", stepNode);

				if (ACTION_TYPE_RUN_SQL.equals(actionType))
				{
					implementationClass = "lumis.upgrade.RunSqlStepHandler";
				}
				else if (ACTION_TYPE_REGISTER_SERVICE.equals(actionType))
				{
					implementationClass = "lumis.upgrade.RegisterServiceStepHandler";
				}
				else if (ACTION_TYPE_UNREGISTER_CONTROL_TYPE.equals(actionType))
				{
					implementationClass = "lumis.doui.upgrade.UnregisterControlTypeStepHandler";
				}
				else if (ACTION_TYPE_CUSTOM.equals(actionType))
				{
					implementationClass = XmlUtil.readNodeString("class", stepNode);
				}

				stepHandler = (IStepHandler) Class.forName(implementationClass).newInstance();
				stepResultNode = stepHandler.executeStep(sessionConfig, stepNode, transaction);

				stepResultNodes.add(stepResultNode);
			}

			String buildResult = getBuildResult(stepResultNodes);

			return buildResult;
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	private String getBuildResult(List<Node> stepResultNodes) throws ManagerException, PortalException
	{
		StringBuilder buildResult = new StringBuilder();

		// TODO Tiago: Translate this
		String outputMessage = "STR_STEPS_EXECUTED";
		String userResultMessage = "";

		buildResult.append("<outputMessage>");
		buildResult.append(stepResultNodes.size() + " " + outputMessage);
		buildResult.append("</outputMessage>");

		for (Node stepResultNode : stepResultNodes)
		{
			if (XmlUtil.selectSingleNode("result/userResultMessage", stepResultNode) != null)
			{
				userResultMessage += "\n - " + XmlUtil.readNodeString("result/userResultMessage", stepResultNode);
			}
		}

		if (userResultMessage.length() > 0)
		{
			// TODO Tiago: Translate this
			buildResult.append("<userResultMessage>");
			buildResult.append("STR_USER_RESULT_MESSAGE" + ":" + userResultMessage);
			buildResult.append("</userResultMessage>");
		}

		return buildResult.toString();
	}

	public void setBuildStatus(SessionConfig sessionConfig, BuildConfig buildConfig, String buildStatus, String userResponse, ITransaction transaction) throws PortalException
	{
		if (!ManagerFactory.getPortalAclManager().checkPermission(sessionConfig, PortalPermissions.MANAGE_PORTAL, transaction))
			throw new AccessDeniedException();
		
		Node upgradeNode = getUpgradeNode();
		Node buildNode = XmlUtil.selectSingleNode("/upgradeConfiguration/build[@majorVersion='" + 
				buildConfig.getMajorVersion() + "' and @minorVersion='" +
				buildConfig.getMinorVersion() + "' and @releaseVersion='" +
				buildConfig.getReleaseVersion() + "' and @buildNumber='" + 
				buildConfig.getBuildNumber() + "' and @buildSequence='" + 
				buildConfig.getBuildSequence() + "']", upgradeNode);

		IUpgradeDao upgradeDao = DaoFactory.getUpgradeDao();
		BuildConfig currentBuild = new BuildConfig(buildNode);
		upgradeDao.setBuildStatus(currentBuild, buildStatus, userResponse, sessionConfig.getUserId(), transaction);
	}
	
	public void markUpgradesDone(SessionConfig sessionConfig, ITransaction transaction) throws ManagerException, PortalException
	{
		// get last build node
		Node upgradeNode = getUpgradeNode();
		Node[] buildNodes = XmlUtil.selectNodes("/upgradeConfiguration/build", upgradeNode);
		if (buildNodes.length == 0)
			throw new UnexpectedException("No build node found");
		Node lastBuildNode = buildNodes[buildNodes.length-1];
		
		// set the last build as current build
		BuildConfig buildConfig = new BuildConfig(lastBuildNode);
		setBuildStatus(sessionConfig, buildConfig, Integer.toString(BUILD_STATUS_SUCCESS), Integer.toString(USER_RESPONSE_EXECUTION_REQUESTED), transaction);
	}
	
	public BuildConfig executeNextPendingBuild(SessionConfig sessionConfig, ITransaction transaction) throws PortalException
	{
		if (!ManagerFactory.getPortalAclManager().checkPermission(sessionConfig, PortalPermissions.MANAGE_PORTAL, transaction))
			throw new AccessDeniedException();
		
		Node upgradeNode = getUpgradeNode();
		IUpgradeDao upgradeDao = DaoFactory.getUpgradeDao();
		BuildConfig currentBuildConfig = upgradeDao.getCurrentBuildInfo(transaction);
		BuildConfig nextBuildConfig = getNextBuildConfig(upgradeNode, currentBuildConfig);
		if (nextBuildConfig != null)
		{
			SessionConfig systemSessionConfig = ManagerFactory.getAuthenticationManager().impersonate(UserConfig.USER_SYSTEM_ID);
			try
			{
				executeBuild(sessionConfig, nextBuildConfig, transaction);				
			}
			catch (PortalException e)
			{
				throw new UpgradeException(e, nextBuildConfig);
			}
			finally
			{
				ManagerFactory.getAuthenticationManager().endImpersonation(systemSessionConfig);
			}
			
			setBuildStatus(sessionConfig, nextBuildConfig, Integer.toString(BUILD_STATUS_SUCCESS), Integer.toString(USER_RESPONSE_EXECUTION_REQUESTED), transaction);
			return nextBuildConfig;
		}
		else
			return null;
	}
	
	/**
	 *
	 */

	public void rebuildLumisAdministration(SessionConfig sessionConfig, ITransaction transaction) throws ManagerException, PortalException
	{
		if (!ManagerFactory.getPortalAclManager().checkPermission(sessionConfig, PortalPermissions.MANAGE_PORTAL, transaction))
			throw new AccessDeniedException();
		
		SessionConfig systemSessionConfig = ManagerFactory.getAuthenticationManager().impersonate(UserConfig.USER_SYSTEM_ID);
		try
		{
			// first delete all children of the LUMIS_ADMIN_CHANNEL
			List<String> subChannelIds = ManagerFactory.getChannelManager().getIdsByParentChannelId(systemSessionConfig, ChannelConfig.LUMIS_ADMIN_CHANNEL_ID, transaction);
			
			for(String subChannelId : subChannelIds)
			{
				ManagerFactory.getChannelManager().delete(systemSessionConfig, subChannelId, transaction);
			}
			
			ZipDirectory zipDirectory;
			try
			{
				zipDirectory = new ZipDirectory(PortalContext.getDefinitionPath("/lumis/portal/LumisAdministration.lec"));
			}
			catch (FileNotFoundException e)
			{
				throw new UnexpectedException("File not found.", e);
			}
			
			DeserializationContext deserializationContext = new DeserializationContext(sessionConfig, transaction, ChannelConfig.LUMIS_ADMIN_CHANNEL_ID, zipDirectory, true, false, true, true, false, ExistingStyleAction.SKIP, false, false);
			ManagerFactoryInternal.getSerializationManager2().deserializeChannel(deserializationContext);
						
			ManagerFactory.getPresentationManager().resetAdministrationBarChannelIds();
			
		}
		finally
		{
			ManagerFactory.getAuthenticationManager().endImpersonation(systemSessionConfig);
		}
	}
	
	public BuildConfig getCurrentBuildInfo(ITransaction transaction) throws ManagerException, PortalException
	{
		return DaoFactory.getUpgradeDao().getCurrentBuildInfo(transaction);
	}

}
